#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<ctype.h>
#include<sys/stat.h>
#include<fcntl.h>

#define LEFT "["
#define RIGHT "]"
#define STYLE "#"
#define DELIM " \t"//分隔符
#define LINE_SIZE 1024
#define ARGV_SIZE 32
#define EXIT_CODE 44
#define NONE -1
#define IN_RDIR 0
#define OUT_RDIR 1
#define APPEND_RDIR 2

char commandline[LINE_SIZE];//存储命令行
char* argv[ARGV_SIZE];//存储解析好的子串集
char pwd[LINE_SIZE];//存储当前工作目录的绝对路径
int quit = 0;
int lastcode = 0;//存储上一个进程的退出码

//环境变量表
extern char **environ;
//自定义环境变量
char myenv[LINE_SIZE];

//需重定向的文件名
char* rdirfilename = NULL;
//重定向类型
int rdir = NONE;

//查找>,>>,< 
//若找到，说明需要重定向
//将>,>>,< 位置设为\0截断命令行，并将该位置后的子串保存到rdirfilename
void check_rdir(char* cmd)
{
    char* pos = cmd;
    while(*pos)
    {
        if(*pos == '>')
        {
            if(*(pos+1) == '>')
            {
                *pos++ = '\0';
                *pos++ = '\0';
                while(isspace(*pos)) pos++;        
                rdirfilename = pos;
                rdir = APPEND_RDIR;
                break;
            }
            else 
            {
                *pos++ = '\0';
                while(isspace(*pos)) pos++;        
                rdirfilename = pos;
                rdir = OUT_RDIR;
                break;
            }
        }
        else if(*pos == '<')
        {
            *pos++ = '\0';
            while(isspace(*pos)) pos++;
            rdirfilename = pos;
            rdir = IN_RDIR;
            break;
        }
        else 
        {
            // do nothing……
        }
        pos++;
    }
}



void interact(char* cline, int size)
{
    printf(LEFT"%s@%s %s"RIGHT""STYLE, getenv("USER"), getenv("HOSTNAME"), getenv("PWD"));
    char* s = fgets(cline, size, stdin);
    assert(s);//命令行不可能为空
    (void)s;//表示该命名行已被用过，强转类型阻止重复使用
    cline[strlen(cline)-1] = '\0';//结束输入命令行时，需要回车结束，这样在尾部会留下\n，需要除去

    check_rdir(cline);
}

int splitstring(char cline[], char* _argv[])
{
    int i = 0;
    _argv[i++] = strtok(cline, DELIM);//先获取第一个子串
    while(_argv[i++] = strtok(NULL, DELIM));//后传NULL作为参数，循环获取后续子串。
    return i - 1;//返回子串个数
}

int buildcommand(char* _argv[], int _argc)
{
    //内建命令cd
    if(_argc == 2 && strcmp(_argv[0], "cd") == 0)
    { 
        //切换目标工作目录
        chdir(_argv[1]);
        //更新环境变量
        getcwd(pwd, sizeof(pwd));//获取当前工作目录绝对路径
        sprintf(getenv("PWD"), "%s", pwd);
        return 1;
    }
    //export
    else if(_argc == 2 && strcmp(_argv[0], "export") == 0)
    {
        strcpy(myenv, _argv[1]);
        putenv(myenv);
        return 1;
    }
   // echo
    else if(_argc == 2 && strcmp(_argv[0], "echo") == 0)
    {
        if(strcmp(_argv[1], "$?") == 0)//查看上一次退出码
        {
            printf("%d\n", lastcode);
            lastcode = 0;
        }
        else if(*_argv[1] == '$')
        {
            char *val = getenv(_argv[1]+1);
            if(val) printf("%s\n", val);
        }
        else
        {
            printf("%s\n", _argv[1]);
        }
        return 1;
    }

    // 特殊处理ls，使字体有颜色
    if(strcmp(_argv[0], "ls") == 0)
    {
        _argv[_argc++] = "--color";
        _argv[_argc] = NULL;
    }
    return 0;
}

void commoncommand(char* _argv[])
{                             
    int id = fork();  
    if(id < 0)//fork错误
    {
        perror("fork");
        return;
    }
    else if(id == 0)//child
    {
        int fd = 0;
        if(rdir == IN_RDIR)
        {
            fd = open(rdirfilename, O_RDONLY, 0666);
            dup2(fd, 0);
        }
        else if(rdir == OUT_RDIR)
        {
            fd = open(rdirfilename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
            dup2(fd, 1);
        }
        else if(rdir == APPEND_RDIR)
        {
            fd = open(rdirfilename, O_WRONLY|O_CREAT|O_APPEND, 0666);
            dup2(fd, 1);
        }

        //进程替换，让子进程执行命令
        execvp(_argv[0], _argv);
        exit(EXIT_CODE);
    }
    else//father
    {
        //阻塞等待子进程
        int status = 0;
        int ret = waitpid(id, &status, 0);
        if(ret == id)
        {
            lastcode = WEXITSTATUS(status);//获取最近一个进程的退出码
        }
    }
    
}

int main()
{
    while(!quit)
    {
         //0. 初始化
         rdirfilename = NULL;
         rdir = NONE;
         //1. 获取命令行
         interact(commandline, sizeof(commandline));
         //测试
         // for(int i = 0; i < sizeof(commandline); ++i ) printf("%c", commandline[i]);
         // printf("\n");
         
         //2. 解析命令行（字符串分隔问题）
         int argc = splitstring(commandline, argv);
         if(argc == 0) continue;//若解析命令行为空，重新输入命令行
         //测试
         //for(int i = 0; i < argc; ++i) printf("%s ", argv[i]);
         //printf("\n");

         //3. 判断命令
         //3.1 内建命令
         int n = buildcommand(argv, argc);

         //3.2 普通命令
         if(!n) commoncommand(argv);
    }

    return 0;
}
